/*
 * Copyright © 2009 Gerald Isaac Schwarz
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package driver;

import java.awt.Color;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class GameSaver {
	private File file;
	private PrintWriter printWriter;
	private Document document;
	private ArrayList<base.Player> players;
	private ArrayList<base.Piece> types;
	private ArrayList<base.Board> boards;
	private base.Engine engine;
	private Driver driver;
	private base.Player currentPlayer;
	
	public GameSaver(Driver driver, File file) {
		this.file = file;
		this.driver = driver;
		this.engine = this.driver.getEngine();
		this.players = this.engine.getPlayers();
		this.currentPlayer = this.engine.getCurrentPlayer();
		this.boards = new ArrayList<base.Board>();
		this.boards.add(this.engine.getBoard());
		this.types = this.engine.getTypes();
		
		if (this.file.exists()) {
			throw new RuntimeException("Specified file already exists.");
		}
		
		try {
			this.file.createNewFile();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			this.printWriter = new PrintWriter(this.file);
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		try {
			this.getDocument();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			this.saveGame();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void printNodeContent(Node node) throws IOException {
		this.printWriter.write("<" + node.getNodeName());
		if (node.hasAttributes()) {
			for (int i = 0; i < node.getAttributes().getLength(); i++) {
				this.printWriter.write(" ");
				this.printWriter.write(node.getAttributes().item(i).getNodeName() + "=");
				this.printWriter.write("\"" + node.getAttributes().item(i).getNodeValue() + "\"");
			}
		}
		this.printWriter.write(">");
		
		if (node.hasChildNodes()) {
			this.printWriter.println();
			for (int i = 0; i < node.getChildNodes().getLength(); i++) {
				if (node.getChildNodes().item(i).getNodeType() == Node.TEXT_NODE) {
					this.printWriter.print("\t");
					this.printWriter.write(node.getChildNodes().item(i).getTextContent());
					this.printWriter.println();
				}
				else {
					printNodeContent(node.getChildNodes().item(i));
				}
			}
		}
		this.printWriter.write("</" + node.getNodeName() + ">");
		this.printWriter.println();
	}
	
	private void saveCurrentPlayer() {
		Element currentPlayerNode = document.createElement("current_player");
		currentPlayerNode.appendChild(document.createTextNode(this.players.indexOf(this.currentPlayer) + ""));
		document.getDocumentElement().appendChild(currentPlayerNode);
	}
	
	private void saveDriver() {
		Element driverNode = document.createElement("driver");
		driverNode.appendChild(document.createTextNode(driver.getClass().getSimpleName()));
		document.getDocumentElement().appendChild(driverNode);
	}
	
	private void saveEngine() {
		Element engineNode = document.createElement("engine");
		engineNode.appendChild(document.createTextNode(engine.getClass().getSimpleName()));
		document.getDocumentElement().appendChild(engineNode);
	}
	
	private void saveTypes() {
		for (base.Piece type : this.types) {
			Element typeNode = this.document.createElement("type");
			typeNode.setAttribute("name", type.getType());
			
			if (type.hasRuleType()) {
				typeNode.setAttribute("rule_type", type.getRuleType());
			}
			
			typeNode.setAttribute("representation", type.getRepresentation());
			
			for (base.Move move : type.getMoves()) {
				Element moveNode = this.document.createElement("move");
				
				Element xNode = this.document.createElement("x");
				xNode.appendChild(this.document.createTextNode(move.getDistance().x + ""));
				
				Element yNode = this.document.createElement("y");
				yNode.appendChild(this.document.createTextNode(move.getDistance().y + ""));
				
				Element repetitionsNode = this.document.createElement("repetitions");
				repetitionsNode.appendChild(this.document.createTextNode(move.getRepetition() + ""));
				
				Element is_moveNode = this.document.createElement("is_move");
				is_moveNode.appendChild(this.document.createTextNode(move.isMove() + ""));
				
				Element is_attackNode = this.document.createElement("is_attack");
				is_attackNode.appendChild(this.document.createTextNode(move.isAttack() + ""));
				
				
				moveNode.appendChild(xNode);
				moveNode.appendChild(yNode);
				moveNode.appendChild(repetitionsNode);
				moveNode.appendChild(is_moveNode);
				moveNode.appendChild(is_attackNode);
				
				typeNode.appendChild(moveNode);
			}
			
			for (String upgrade : type.getUpgradeOptions()) {
				Element upgradeNode = this.document.createElement("upgrade");
				upgradeNode.setAttribute("name", upgrade);
				typeNode.appendChild(upgradeNode);
			}
			
			this.document.getDocumentElement().appendChild(typeNode);
		}
	}
	
	private void savePlayers() {
		for (base.Player player : this.players) {
			Element playerNode = this.document.createElement("player");
			playerNode.setAttribute("id", player.getName());
			
//			TODO create some sort of implementation that doesn't require reflection.
			
			for (int i = 0; i < Color.class.getFields().length; i++) {
				Field colorField = Color.class.getFields()[i];
				
				try {
					if (colorField.get(null) instanceof Color) {
						Color color = (Color) colorField.get(null);
						
						if (player.getColor().equals(color)) {
							playerNode.setAttribute("color", colorField.getName());
						}
					}
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (DOMException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					playerNode.setAttribute("color", "lightGray");
				}
			}
			
			playerNode.setAttribute("direction", player.getDirection() + "");
			
			this.document.getDocumentElement().appendChild(playerNode);
		}
	}
	
	private void saveBoards() {
		for (base.Board board : this.boards) {
			Element boardNode = this.document.createElement("board");
			boardNode.setAttribute("width", board.getSize().width + "");
			boardNode.setAttribute("height", board.getSize().height + "");
			
			for (base.UpgradeZone upgradeZone : board.getUpgradeZones()) {
				Element upgradeZoneNode = this.document.createElement("upgrade_zone");
				upgradeZoneNode.setAttribute("x", upgradeZone.getZone().x + "");
				upgradeZoneNode.setAttribute("y", upgradeZone.getZone().y + "");
				upgradeZoneNode.setAttribute("height", upgradeZone.getZone().height + "");
				upgradeZoneNode.setAttribute("width", upgradeZone.getZone().width+ "");
				
				for (base.Player player : upgradeZone.getPlayers()) {
					Element idNode = this.document.createElement("id");
					idNode.appendChild(this.document.createTextNode(this.players.indexOf(player)+ ""));
					upgradeZoneNode.appendChild(idNode);
				}
				
				boardNode.appendChild(upgradeZoneNode);
			}
			
			for (base.Piece piece : board.getPieces()) {
				Element pieceNode = this.document.createElement("piece");
				pieceNode.setAttribute("type", piece.getType());
				pieceNode.setAttribute("player", this.players.indexOf(piece.getOwner()) + "");
				pieceNode.setAttribute("x", piece.getLocation().x + "");
				pieceNode.setAttribute("y", piece.getLocation().y + "");
				pieceNode.setAttribute("is_alive", piece.isAlive() + "");
				
				if (piece.getUpgrade() != piece) {
					pieceNode.setAttribute("upgrade", piece.getUpgrade().getType());
				}
				
				boardNode.appendChild(pieceNode);
			}
			
			this.document.getDocumentElement().appendChild(boardNode);
		}
	}
	
	private void saveGame() throws IOException {
		this.saveCurrentPlayer();
		this.saveDriver();
		this.saveEngine();
		this.saveTypes();
		this.savePlayers();
		this.saveBoards();
		
		this.printWriter.println("<?xml version=\"1.0\"?>");
		this.printWriter.println("<!DOCTYPE saved_game [");
		this.printWriter.println("<!ELEMENT saved_game (current_player, driver, engine, type+, player, player+, board+)>");
		this.printWriter.println("<!ELEMENT current_player (#PCDATA)>");
		this.printWriter.println("<!ELEMENT driver (#PCDATA)>");
		this.printWriter.println("<!ELEMENT engine (#PCDATA)>");
		this.printWriter.println("<!ELEMENT type (move+, upgrade*)>");
		this.printWriter.println("<!ATTLIST type name CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST type rule_type CDATA #IMPLIED>");
		this.printWriter.println("<!ATTLIST type representation CDATA #REQUIRED>");
		this.printWriter.println("<!ELEMENT move (x, y, repetitions, is_move, is_attack)>");
		this.printWriter.println("<!ELEMENT x (#PCDATA)>");
		this.printWriter.println("<!ELEMENT y (#PCDATA)>");
		this.printWriter.println("<!ELEMENT repetitions (#PCDATA)>");
		this.printWriter.println("<!ELEMENT is_move (#PCDATA)>");
		this.printWriter.println("<!ELEMENT is_attack (#PCDATA)>");
		this.printWriter.println("<!ELEMENT upgrade EMPTY>");
		this.printWriter.println("<!ATTLIST upgrade name CDATA #REQUIRED>");
		this.printWriter.println("<!ELEMENT player EMPTY>");
		this.printWriter.println("<!ATTLIST player id CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST player direction CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST player color CDATA #REQUIRED>");
		this.printWriter.println("<!ELEMENT board (upgrade_zone*, piece*)>");
		this.printWriter.println("<!ATTLIST board width CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST board height CDATA #REQUIRED>");
		this.printWriter.println("<!ELEMENT upgrade_zone (id+)>");
		this.printWriter.println("<!ATTLIST upgrade_zone x CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST upgrade_zone y CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST upgrade_zone height CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST upgrade_zone width CDATA #REQUIRED>");
		this.printWriter.println("<!ELEMENT id (#PCDATA)>");
		this.printWriter.println("<!ELEMENT piece EMPTY>");
		this.printWriter.println("<!ATTLIST piece type CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST piece player CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST piece is_alive CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST piece x CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST piece y CDATA #REQUIRED>");
		this.printWriter.println("<!ATTLIST piece upgrade CDATA #IMPLIED>");
		this.printWriter.println("]>");
		
		this.printNodeContent(this.document.getDocumentElement());
		
		this.printWriter.flush();
		this.printWriter.close();
	}
	
	private void getDocument() throws IOException, SAXException, ParserConfigurationException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setValidating(true);
//		dbf.setNamespaceAware(true);
		dbf.setIgnoringElementContentWhitespace(true);
		this.document = dbf.newDocumentBuilder().newDocument();
		
		Element documentElement= document.createElement("saved_game");
		document.appendChild(documentElement);
	}
	
	
}
